// 我们要知道哪个属性被修改了,我们才能对页面上的内容进行更新。
// 所以我们必须先能够捕获修改的这个事件

import { renderData } from "./render.js";
import { rebuild } from "./mount.js";
import { getValue } from "../util/ObjectUtil.js";

// 所以我们需要用代理的方式来实现监听属性修改
const arrayProto = Array.prototype; // 获取Array的原型
function defArrayFunc(obj, func, namespace, vm) {
  Object.defineProperty(obj, func, {
    enumerable: true, // 对象属性是否可通过for-in循环,flase为不可循环,默认值为true
    configurable: true, // 能否使用delete、能否需改属性特性、或能否修改访问器属性,false为不可重新定义,默认值为true
    value: function (...args) {
      // 以下为在不使用代理时的原始功能
      let original = arrayProto[func];
      const result = original.apply(this, args);
      // 对象属性的默认值，默认值为undefined
      console.log(getNameSpace(namespace, ""));
      // 每次更改数据后节点重新构建(主要是要处理v-for语句中数据的改变导致全部语句内节点都要改变)
      rebuild(vm, getNameSpace(namespace, ""));
      // 每次更改数据后节点重新渲染模板中的数据(主要针对非v-for操作),需要传入命名空间getNameSpace(namespace, prop)
      renderData(vm, getNameSpace(namespace, ""));
      return result;
    },
  });
}
function proxyArr(vm, arr, namespace) {
  let obj = {
    eleType: "Array",
    toString() {
      let result = "";
      for (let i = 0; i < arr.length; i++) {
        result += arr[i] + ",";
      }
      return result.substring(0, result.length - 1);
    },
    // push() {},
    // pop() {},
    // shift() {},
    // unshift() {},
  };
  defArrayFunc.call(vm, obj, "push", namespace, vm);
  defArrayFunc.call(vm, obj, "pop", namespace, vm);
  defArrayFunc.call(vm, obj, "shift", namespace, vm);
  defArrayFunc.call(vm, obj, "unshift", namespace, vm);
  arr.__proto__ = obj;
  return arr;
}

function constructObjectProxy(vm, obj, namespace) {
  // 代理对象为对象时的代理生成器
  let proxyObj = {};
  for (let prop in obj) {
    // 给due的代理对象(_data对象)添加代理
    Object.defineProperty(proxyObj, prop, {
      configurable: true, // 能否使用delete、能否需改属性特性、或能否修改访问器属性、,false为不可重新定义,默认值为true
      get() {
        return obj[prop];
      },
      set(val) {
        // set的副作用,也是最重要的作用
        console.log(getNameSpace(namespace, prop)); // 打印命名空间,表示当前修改的是什么下面的东西
        obj[prop] = val; // set最基本的作用
        // 每次更改数据后重新渲染模板中的数据，需要传入命名空间getNameSpace(namespace, prop)
        renderData(vm, getNameSpace(namespace, prop));
      },
    });
    // 给due自己的data添加代理
    Object.defineProperty(vm, prop, {
      configurable: true,
      get() {
        return obj[prop];
      },
      set(val) {
        console.log(getNameSpace(namespace, prop));
        obj[prop] = val;
        // 对于数组类型的数据proxyArr中只重写了push、pop、shift、unshift方法，如果整体替换的话需要在这里做处理
        let value = getValue(vm._data, getNameSpace(namespace, prop));
        if (value instanceof Array) {
          // 如果是数组，就重建节点
          rebuild(vm, getNameSpace(namespace, prop))
          // 然后再重新渲染
          renderData(vm, getNameSpace(namespace, prop));
        } else {
          // 每次更改数据后重新渲染模板中的数据，需要传入命名空间getNameSpace(namespace, prop)
          renderData(vm, getNameSpace(namespace, prop));
        }
      },
    });
    // 使用递归深层代理对象类型的属性（当代理对象的属性为对象时）
    if (obj[prop] instanceof Object) {
      proxyObj[prop] = constructProxy(
        vm,
        obj[prop],
        getNameSpace(namespace, prop)
      );
    }
  }
  return proxyObj;
}
// 修改命名空间的方法
function getNameSpace(nowNameSpace, nowProp) {
  // nowNameSpace当前命名空间, nowProp当前属性
  if (nowNameSpace === null || nowNameSpace === "") {
    // 如果当前命名空间为空就返回当前属性
    return nowProp;
  } else if (nowProp === null || nowProp === "") {
    // 如果当前属性为空就返回当前命名空间
    return nowNameSpace;
  } else {
    // 否则返回当前命名空间.当前属性
    return nowNameSpace + "." + nowProp;
  }
}
// 导出构造代理方法
export function constructProxy(vm, obj, namespace) {
  // vm表示Due对象,obj表示要进行代理的对象,namespace
  // 递归
  let proxyObj = null;
  if (obj instanceof Array) {
    // 代理对象为数组时
    let len = obj.length;
    proxyObj = new Array(len);
    for (let i = 0; i < len; i++) {
      // 给数组里的每一个元素做代理
      proxyObj[i] = constructProxy(vm, obj[i], namespace);
    }
    // 给数组做代理(监听数组的修改,比如pop,push...)
    proxyObj = proxyArr(vm, obj, namespace);
  } else if (obj instanceof Object) {
    // 代理对象为对象时
    proxyObj = constructObjectProxy(vm, obj, namespace); // 够造代理对象为对象时的代理
  } else {
    throw new Error("error");
  }
  return proxyObj;
}
